'use strict'

const assert = require('assert');
// 参考: http://nodejs.cn/api/stream.html
const stream = require('readable-stream');

const util = require('./util');
const logger = require('./logger')('PeerConnection');

// 连接超时时间
const CONNECT_TIMEOUT_TIME = 25000;

// 获取rtc
function getRTC() {
  if (typeof window === 'undefined') {
    return require('wrtc');
  };
  const wrtc = {
    RTCPeerConnection: window.RTCPeerConnection || window.mozRTCPeerConnection ||
      window.webkitRTCPeerConnection,
    RTCSessionDescription: window.RTCSessionDescription ||
      window.mozRTCSessionDescription || window.webkitRTCSessionDescription,
    RTCIceCandidate: window.RTCIceCandidate || window.mozRTCIceCandidate ||
      window.webkitRTCIceCandidate
  }
  if (!wrtc.RTCPeerConnection) {
    return require('wrtc');
  }
  return wrtc;
}
const wrtc = getRTC();
const RTCPeerConnection = wrtc.RTCPeerConnection;
const RTCSessionDescription = wrtc.RTCSessionDescription;
const RTCIceCandidate = wrtc.RTCIceCandidate;


/**
 * PeerConnection
 *    事件:
 *        'open': 与远端连接成功后触发
 *        'data': 获取到数据时触发
 *        'error': 出现错误时触发,附加参数 'error'
 *        'close': 关闭后触发
 */
class PeerConnection extends stream.Duplex {
  constructor(peerId, peerManager, options) {

    options = Object.assign({
      allowHalfOpen: false, // stream.Duplex: 如果设置为false, 那么当读端停止时，写端自动停止
      readableHighWaterMark: 16384, // stream.Duplex: 读缓存上限
      writableHighWaterMark: 16384, // stream.Duplex: 写缓存上限
    }, options);

    super(options);
    this.peerId = peerId;
    this.connectionId = options.connectionId ? options.connectionId : this._generateConnectionId();   // 主要用于区别同一个peer的不同连接
    this._rtcConfig = options.rtcConfig;
    this._isOriginator = !!options.isOriginator;    // 是否是发起连接请求的发起端
    this._peerManager = peerManager;
    this._dataChannelLabel = this.connectionId;
    this._rtc_pc = null;                // webRTC Peer Connection
    this._rtc_dc = null;                // webRTC Data Channel

    this.isOpened = false;
    this.isClosed = false;

    this._connectTimeOutTimer = null;
    this._startConnection();

    // this.on('data', data => {
    //   logger.debug(`[recv] <<<<<<<<<<< ${data.length}`);
    // });
  }

  // Duplex stream method.  push
  _read() {}

  // Duplex stream method.  write
  _write(chunk, encoding, cb) {
    // logger.debug('_write');
    this.send(chunk);
    cb(null);
  }

  // 生成连接id
  _generateConnectionId() {
    return Math.random().toString(36).substr(2);
  }

  // sdp(Session Description Protocol) offer
  _makeOffer() {
    logger.debug(`create offer.`);
    this._rtc_pc.createOffer().then(offer => {
      logger.debug(`set local description.`);
      this._rtc_pc.setLocalDescription(offer, () => {
        // logger.debug(`send offer meesage to remote peer.`);
        this._peerManager.sendSignalMessage(this.peerId, 'CONNECT_OFFER', {
          'sdp': this._rtc_pc.localDescription,
          'connectionId': this.connectionId,
        }).catch(error => {
          this.emit('error', error);
        });
      }, error => {
        const errStr = `webrtc setLocalDescription failed: ${error.toString()}`;
        logger.error(errStr);
        this.emit('error', new Error(errStr));
      });
    }).catch(error => {
      const errStr = `webrtc createOffer failed: ${error.toString()}`;
      logger.error(errStr);
      this.emit('error', new Error(errStr));
    });
  }

  // sdp(Session Description Protocol) answer
  _makeAnswer() {
    logger.debug(`create answer.`);
    this._rtc_pc.createAnswer().then(answer => {
      logger.debug(`set local description.`);
      this._rtc_pc.setLocalDescription(answer, () => {
        // logger.debug(`send answer meesage to remote peer.`);
        this._peerManager.sendSignalMessage(this.peerId, 'CONNECT_ANSWER', {
          'sdp': this._rtc_pc.localDescription,
          'connectionId': this.connectionId,
        }).catch(error => {
          this.emit('error', error);
        });
      }, error => {
        const errStr = `webrtc setLocalDescription failed: ${error.toString()}`;
        logger.error(errStr);
        this.emit('error', new Error(errStr));
      });
    }).catch(error => {
      const errStr = `webrtc createAnswer failed: ${error.toString()}`;
      logger.error(errStr);
      this.emit('error', new Error(errStr));
    });
  }

  _startConnectTimer() {
    clearTimeout(this._connectTimeOutTimer);
    this._connectTimeOutTimer = setTimeout(() => {
      logger.error(`connect time out.`);
      this.emit('error', new Error('Connect time out.'));
    }, CONNECT_TIMEOUT_TIME);
  }

  _clearConnectTimer() {
    if (this._connectTimeOutTimer) {
      clearTimeout(this._connectTimeOutTimer);
      this._connectTimeOutTimer = null;
    }
  }

  // 处理 data channel 收到的数据
  _handleDCMessage(event) {
    if (!this.isOpened) {
      return;
    }
    let data = event.data;
    window.myData = data;
    if (data instanceof ArrayBuffer) {
      data = Buffer.from(data);
    }
    this.push(data); // 继承"stream.Duplex"
  }

  _setupDCListeners() {
    // on open
    this._rtc_dc.onopen = event => {
      logger.debug(`connection to ${this.peerId} is success.`);
      this.isOpened = true;
      this._clearConnectTimer();
      this.emit('open');
    };

    // on data
    this._rtc_dc.onmessage = event => {
      // logger.debug(`received data from ${this.peerId}.`);
      this._handleDCMessage(event);
      // const data = event.data;
      // this.emit('data', data);
    };

    // on error
    this._rtc_dc.onerror = error => {
      logger.debug(`connection to ${this.peerId} occur an error: ${error.toString()}.`);
      this.emit('error', error);
    }

    // on close
    this._rtc_dc.onclose = event => {
      logger.debug(`connection to ${this.peerId} is closed.`);
      this.close();
    };
  }

  _clearDCListeners() {
    this._rtc_dc.onopen = null;
    this._rtc_dc.onmessage = null;
    this._rtc_dc.onerror = null;
    this._rtc_dc.onclose = null;
  }

  // 设置 data channel
  _setupDC(dc) {
    this._rtc_dc = dc;
    this._rtc_dc.binaryType = 'arraybuffer';

    this._setupDCListeners();
  }

  _setupPCListeners() {
    // ICE Candidates
    // logger.debug('listen ICE candidates.');
    this._rtc_pc.onicecandidate = event => {
      logger.debug(`ICE candidate: ${event.candidate ? event.candidate.candidate : '(null)'}.`);
      if (event.candidate) {
        // logger.debug(`received ICE candidates from ${this.peerId}`);
        this._peerManager.sendSignalMessage(this.peerId, 'CONNECT_CANDIDATE', {
          candidate: event.candidate,
          connectionId: this.connectionId
        }).catch(error => {
          this.emit('error', error);
        });
      }
    };

    // logger.debug('listen connection state change.');
    this._rtc_pc.onconnectionstatechange = event => {
      logger.debug(event);
    }

    // logger.debug('listen ICE connection state change.');
    this._rtc_pc.oniceconnectionstatechange = event => {
      const pc = event.currentTarget;
      switch (pc.iceConnectionState) {
        case 'failed':
          logger.debug(`iceConnectionState is failed, closing connections to ${this.peerId}`);
          this.emit(
            'error',
            new Error(`negotiation of connection to ${this.peerId} failed.`)
          );
          break;

        case 'disconnected':
          logger.debug(`iceConnectionState is disconnected, closing connections to ${this.peerId}`);
          this.close();
          break;

        case 'connected':
          logger.debug(`connection to ${this.peerId} is connected.`);
          this._clearConnectTimer();
          break;

        case 'completed':
          logger.debug(`connection to ${this.peerId} is completed.`);
          // this._rtc_pc.onicecandidate = util.noop;
          break;

        case 'closed':
          logger.debug(`remote peer closed connect.`);
          this.close();
          break;
      }
    };
  }

  _clearPCListeners() {
    this._rtc_pc.onicecandidate = null;
    this._rtc_pc.onconnectionstatechange = null;
    this._rtc_pc.oniceconnectionstatechange = null;
  }

  // 连接到远端节点
  _startConnection() {
    logger.debug(`create RTCPeerConnection${this._isOriginator ? ' [originator]': ''} for ${this.peerId}.`);
    this._startConnectTimer();

    this._rtc_pc = new RTCPeerConnection(this._rtcConfig);
    this._setupPCListeners();
    // 发起端
    if (this._isOriginator) {
      this._peerManager.sendSignalMessage(this.peerId, 'CONNECT_REQUEST', {
        connectionId: this.connectionId
      }).catch(error => {
        this.emit('error', error);
      });
      logger.debug('create data channel.');
      const config = {
        ordered: true, // 数据有序

      };
      const dc = this._rtc_pc.createDataChannel(this._dataChannelLabel, config);
      this._setupDC(dc);
      this._makeOffer();

      // 被连接端
    } else {
      // logger.debug('listening for data channel callback.');
      this._rtc_pc.ondatachannel = (event) => {
        logger.debug('received data channel callback.');
        const dc = event.channel;
        this._setupDC(dc);
      }
    }
  }

  // 处理 sdp(Session Description Protocol)
  _handleSdp(type, sdp) {
    sdp = new RTCSessionDescription(sdp);
    logger.debug(`setting remote description.`);
    this._rtc_pc.setRemoteDescription(sdp, () => {
      if (type === 'offer') {
        this._makeAnswer();
      }
    }, error => {
      const errStr = `webrtc setLocalDescription failed: ${error.toString()}`;
      logger.error(errStr);
      this.emit('error', new Error(errStr));
    });
  }

  // 处理 ice candidate
  _handleCandidate(candidate) {
    logger.debug(`add ice candidate`)
    this._rtc_pc.addIceCandidate(
      new RTCIceCandidate(candidate, () => {}, error => {
        const errStr = `webrtc addIceCandidate failed: ${error.toString()}`;
        logger.error(errStr);
        this.emit('error', new Error(errStr));
      })
    );
  }

  /**
   * 处理连接请求
   * @param {*} message
   */
  handleConnectMessage(message) {
    const type = message.type
      , peerId = message.peerId
      , payload = message.payload ? message.payload : {};

    switch (type) {
      case 'CONNECT_OFFER':
        this._handleSdp('offer', payload.sdp);
        break;

      case 'CONNECT_ANSWER':
        this._handleSdp('answer', payload.sdp);
        break;

      case 'CONNECT_CANDIDATE':
        this._handleCandidate(payload.candidate);
        break;
    }
  }

  getStats(cb) {
    // Promise-based getStats() (standard)
    if (this._rtc_pc.getStats.length === 0) {
      this._rtc_pc.getStats().then(res => {
        const reports = []
        res.forEach(report => {
          reports.push(report)
        })
        cb(null, reports)
      }, err => {
        cb(err)
      })

    // Two-parameter callback-based getStats() (deprecated, former standard)
    } else if (this._isReactNativeWebrtc) {
      this._rtc_pc.getStats(null, res => {
        var reports = []
        res.forEach(report => {
          reports.push(report)
        })
        cb(null, reports)
      }, err => {
        cb(err)
      })

    // Single-parameter callback-based getStats() (non-standard)
    } else if (this._rtc_pc.getStats.length > 0) {
      this._rtc_pc.getStats(res => {
        // If we destroy connection in `connect` callback this code might happen to run when actual connection is already closed
        if (this.destroyed) return

        const reports = []
        res.result().forEach(result => {
          const report = {}
          result.names().forEach(name => {
            report[name] = result.stat(name)
          })
          report.id = result.id
          report.type = result.type
          report.timestamp = result.timestamp
          reports.push(report)
        })
        cb(null, reports)
      }, err => {
        cb(err)
      })

      // Unknown browser, skip getStats() since it's anyone's guess which style of
      // getStats() they implement.
    } else {
      cb(null, [])
    }
  }

  /**
   * 发送数据
   * @param {Buffer|Uint8Array} buffer
   */
  send(buffer) {
    assert(buffer instanceof Buffer || buffer instanceof Uint8Array, 'Buffer or Unit8Array need');
    if (!this.isOpened) {
      this.emit('error', new Error('Connection is not open, You shoud listen "open" event before sending message.'));
      return;
    }
    // logger.debug(`[send]>>>>>>>>>>>${buffer.length}`);
    this._rtc_dc.send(buffer);
  }

  _clearUp() {
    this._clearConnectTimer();
    // stream.Duplex
    if (this.readable) this.push(null);
    if (this.writable) this.end();

    if (this._rtc_dc) {
      this._rtc_dc.close();
      this._clearDCListeners();
      this._rtc_dc = null;
    }

    if (this._rtc_pc) {
      this._rtc_pc.close();
      this._clearPCListeners();
      this._rtc_pc = null;
    }
  }

  /**
   * 关闭连接
   */
  close() {
    if (this.isClosed) return;

    this._clearUp();
    this.isOpened = false;
    this.isClosed = true;
    this.emit('close');
  }
}

module.exports.PeerConnection = PeerConnection;
